package com.whfc.mach.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.whfc.common.constant.QueueConst;
import com.whfc.common.exception.BizException;
import com.whfc.common.file.FileHandler;
import com.whfc.common.file.FilePathConfig;
import com.whfc.common.file.properties.FileExpirationRules;
import com.whfc.common.result.ListData;
import com.whfc.common.result.PageData;
import com.whfc.common.result.ResultEnum;
import com.whfc.common.util.DateUtil;
import com.whfc.common.util.FileUtil;
import com.whfc.common.util.MathUtil;
import com.whfc.common.util.PageUtil;
import com.whfc.influx.dao.AppDeviceObdLogDao;
import com.whfc.mach.dao.AppMachMapper;
import com.whfc.mach.dao.AppMaintainItemMapper;
import com.whfc.mach.dao.AppMaintainMapper;
import com.whfc.mach.dao.AppMaintainRuleMapper;
import com.whfc.mach.dto.AppMachDTO;
import com.whfc.mach.dto.AppMachDaySegDTO;
import com.whfc.mach.dto.obd.*;
import com.whfc.mach.entity.AppMach;
import com.whfc.mach.entity.AppMaintain;
import com.whfc.mach.enums.MaintainRuleType;
import com.whfc.mach.enums.MaintainState;
import com.whfc.mach.enums.MaintainType;
import com.whfc.mach.manager.ObdDataMgr;
import com.whfc.mach.manager.util.MachSegmentUtil;
import com.whfc.mach.manager.util.MachUtil;
import com.whfc.mach.param.MaintainHandleParam;
import com.whfc.mach.param.MaintainRuleSetParam;
import com.whfc.mach.service.ObdMaintainService;
import org.apache.dubbo.config.annotation.DubboService;
import org.apache.poi.hssf.usermodel.HSSFCellStyle;
import org.apache.poi.hssf.usermodel.HSSFRow;
import org.apache.poi.hssf.usermodel.HSSFSheet;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.*;

/**
 * @author sun_guodong
 * @description
 * @date 2021-03-05
 */
@DubboService(interfaceClass = ObdMaintainService.class, version = "1.0.0")
public class ObdMaintainServiceImpl implements ObdMaintainService {
    private Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private AppMaintainMapper maintainMapper;

    @Autowired
    private AppMaintainRuleMapper maintainRuleMapper;

    @Autowired
    private AppDeviceObdLogDao appDeviceObdLogDao;

    @Autowired
    private AppMachMapper machMapper;

    @Autowired
    private AmqpTemplate amqpTemplate;

    @Autowired
    private ObdDataMgr obdDataMgr;

    @Autowired
    private AppMaintainItemMapper maintainItemMapper;

    @Autowired
    private FilePathConfig filePathConfig;

    @Autowired
    private FileHandler fileHandler;

    private static final String WEBSOCKET_CONTENT = "有%s台设备需要保养,请您及时处理";

    private static final String HEAD = "%s_设备保养记录";

    /**
     * 临时文件目录
     */
    private static final String PATH = "mach/tmp";


    @Override
    public PageData<ObdMaintainDTO> list(List<Integer> deptIds, Integer pageNum, Integer pageSize, Integer state, Date startTime, Date endTime) throws BizException {
        logger.info("获取设备保养列表服务,deptIds:{},pageNum:{},pageSize:{},state:{},startTime:{},endTime:{}",
                deptIds.toString(), pageNum, pageSize, state, startTime, endTime);
        PageHelper.startPage(pageNum, pageSize);
        List<ObdMaintainDTO> list = maintainMapper.selectMaintainList(deptIds, state, startTime, endTime);
        PageHelper.clearPage();
        for (ObdMaintainDTO obdMaintainDTO : list) {
            if (MaintainState.MAINTAIN.getValue().equals(obdMaintainDTO.getState())) {
                List<String> itemList = maintainItemMapper.selectByMaintainId(obdMaintainDTO.getMaintainId());
                obdMaintainDTO.setItemList(itemList);
            }
        }
        return PageUtil.pageData(PageInfo.of(list));
    }

    @Override
    public MaintainNumStatDTO numStat(List<Integer> deptIds) throws BizException {
        logger.info("设备保养数量统计服务,deptIds:{}", deptIds.toString());
        List<ObdMaintainDTO> list = maintainMapper.selectMaintainList(deptIds, null, null, null);
        Integer total = list.size();
        Integer unMaintainNum = 0;
        Integer maintainNum = 0;
        for (ObdMaintainDTO maintainDTO : list) {
            if (MaintainState.MAINTAIN.getValue().equals(maintainDTO.getState())) {
                maintainNum++;
            } else {
                unMaintainNum++;
            }
        }
        MaintainNumStatDTO data = new MaintainNumStatDTO();
        data.setMaintainNum(maintainNum);
        data.setUnMaintainNum(unMaintainNum);
        data.setTotal(total);
        return data;
    }

    @Override
    public ObdMaintainDTO detail(Integer maintainId) throws BizException {
        logger.info("获取设备保养详情服务,maintainId:{}", maintainId);
        AppMaintain appMaintain = maintainMapper.selectByPrimaryKey(maintainId);
        if (appMaintain == null) {
            throw new BizException(ResultEnum.NOT_FOUND.getCode(), "未找到该保养信息");
        }
        Date endTime = appMaintain.getMaintainTime() == null ? new Date() : appMaintain.getMaintainTime();
        Date latestMaintainTime = maintainMapper.selectLatestMaintainTime(appMaintain.getMachId(), endTime);
        ObdMaintainDTO data = new ObdMaintainDTO();
        BeanUtils.copyProperties(appMaintain, data);
        if (latestMaintainTime != null) {
            data.setIntervalDays(DateUtil.getDaysBettweenDate(latestMaintainTime, endTime));
            int workTimes = this.getWorkTimes(appMaintain.getMachId(), latestMaintainTime, endTime);
            data.setIntervalHours(MathUtil.round(1.0 * workTimes / 3600, 2));
        }
        // 查询保养项目
        List<String> itemList = maintainItemMapper.selectByMaintainId(maintainId);
        data.setItemList(itemList);
        return data;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handle(MaintainHandleParam param) throws BizException {
        logger.info("处理设备保养服务,param:{}", param.toString());
        Integer maintainId = param.getMaintainId();
        Date time = new Date();

        if (maintainId == null) {
            AppMach appMach = machMapper.selectByPrimaryKey(param.getMachId());
            if (appMach == null) {
                throw new BizException(ResultEnum.NOT_FOUND.getCode(), "没找到该保养的设备");
            }
            // 添加设备保养信息
            AppMaintain record = new AppMaintain();
            record.setMachTypeName(appMach.getMachTypeName());
            record.setMachId(appMach.getId());
            record.setMachCode(appMach.getMachCode());
            record.setDeptId(appMach.getHoldDeptId());
            record.setMaintainTime(time);
            record.setMaintainUserId(param.getMaintainUserId());
            record.setMaintainUserName(param.getMaintainUserName());
            record.setType(MaintainType.MANUAL.getValue());
            record.setRemark(param.getRemark());
            record.setState(MaintainState.MAINTAIN.getValue());
            maintainMapper.insertSelective(record);
            maintainId = record.getId();
        } else {
            // 更新设备保养状态
            maintainMapper.updateStateByMaintainId(maintainId, param.getRemark(), time, param.getMaintainUserId(), param.getMaintainUserName());
        }
        // 添加保养项目信息
        maintainItemMapper.batchInsert(maintainId, param.getItemList());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void ruleSet(MaintainRuleSetParam param) throws BizException {
        logger.info("设备保养触发条件设置服务,param:{}", param.toString());
        Integer deptId = param.getDeptId();
        maintainRuleMapper.deleteLogicByDeptId(param.getDeptId());
        List<MaintainRuleDTO> ruleList = param.getRuleList();
        if (ruleList != null && ruleList.size() > 0) {
            maintainRuleMapper.batchInsert(deptId, ruleList);
        }
    }

    @Override
    public ListData<MaintainRuleDTO> ruleDetail(Integer deptId) throws BizException {
        logger.info("设备保养触发条件查询服务,deptId:{}", deptId);
        List<MaintainRuleDTO> list = maintainRuleMapper.selectByDeptId(deptId);
        return new ListData<>(list);
    }

    @Override
    @Async
    @Transactional(rollbackFor = Exception.class)
    public void maintainCheck(List<Integer> deptIds) throws BizException {
        logger.info("设备保养检测服务");
        Map<Integer, List<MaintainRuleDTO>> map = this.getCheckRules();

        for (Integer deptId : deptIds) {
            List<MaintainRuleDTO> ruleDTOList = map.get(deptId);
            if (ruleDTOList == null || ruleDTOList.size() == 0) {
                continue;
            }
            List<AppMaintain> list = new ArrayList<>();
            List<AppMachDTO> machList = machMapper.selectObdMach(deptId);
            for (AppMachDTO machDTO : machList) {
                this.check(list, machDTO, ruleDTOList);
            }

            if (list.size() > 0) {
                maintainMapper.batchInsert(list);
            }
            Integer cnt = maintainMapper.countByDeptId(deptId);
            logger.info("项目:{}需要保养的设备数:{}", deptId, cnt);

            if (cnt > 0) {
                // 发送websocket消息
                MaintainWebsocketDTO data = new MaintainWebsocketDTO();
                data.setContent(String.format(WEBSOCKET_CONTENT, cnt));
                data.setDeptId(deptId);
                amqpTemplate.convertAndSend(QueueConst.OBD_MAINTAIN_DATA, JSONObject.toJSONString(data));
            }
        }


    }

    @Override
    public PageData<ObdMaintainDTO> getMaintainData(Integer machId, Integer pageNum, Integer pageSize) throws BizException {
        logger.info("获取设备保养信息服务,machId:{},pageNum:{},pageSize:{}", machId, pageNum, pageSize);
        PageHelper.startPage(pageNum, pageSize);
        List<ObdMaintainDTO> list = maintainMapper.selectByMachId(machId);
        PageHelper.clearPage();

        for (ObdMaintainDTO maintainDTO : list) {
            List<String> itemList = maintainItemMapper.selectByMaintainId(maintainDTO.getMaintainId());
            maintainDTO.setItemList(itemList);
        }
        return PageUtil.pageData(PageInfo.of(list));
    }

    @Override
    public String export(Integer machId) throws BizException {
        logger.info("导出设备保养信息服务,machId:{}", machId);
        AppMach appMach = machMapper.selectByPrimaryKey(machId);
        if (appMach == null) {
            throw new BizException(ResultEnum.NOT_FOUND.getCode(), "该设备不存在");
        }
        String upload = "";
        // 获取数据
        List<ObdMaintainDTO> list = maintainMapper.selectByMachId(machId);
        if (list.size() == 0) {
            throw new BizException(ResultEnum.FAILURE.getCode(), "无数据");
        }
        for (ObdMaintainDTO maintainDTO : list) {
            List<String> itemList = maintainItemMapper.selectByMaintainId(maintainDTO.getMaintainId());
            String items = String.join("、", itemList);
            maintainDTO.setItems(items);
        }
        try {
            // 获取模板
            ClassPathResource resource = new ClassPathResource("templates/maintainTemplate.xls");
            InputStream templateFileInputStream = resource.getInputStream();
            File tempFile = FileUtil.copyTemplateFile(templateFileInputStream);

            // 写数据
            FileInputStream fileInputStream = new FileInputStream(tempFile);
            HSSFWorkbook workbook = new HSSFWorkbook(fileInputStream);
            HSSFSheet sheet = workbook.getSheetAt(0);


            // 填充数据
            String headStr = String.format(HEAD, appMach.getMachTypeName() + appMach.getMachCode());
            HSSFRow head = sheet.getRow(0);
            Cell cell0 = head.createCell(0);
            cell0.setCellValue(headStr);
            // 居中
            HSSFCellStyle titleStyle = workbook.createCellStyle();
            titleStyle.setAlignment(HorizontalAlignment.CENTER);
            cell0.setCellStyle(titleStyle);
            int rowIdx = 2;
            for (ObdMaintainDTO maintainDTO : list) {
                HSSFRow row = sheet.createRow(rowIdx);
                row.createCell(0).setCellValue(DateUtil.formatDate(maintainDTO.getMaintainTime(), "yyyy-MM-dd HH:mm:ss"));
                row.createCell(1).setCellValue(maintainDTO.getMaintainUserName());
                row.createCell(2).setCellValue(maintainDTO.getType() == 1 ? "系统提醒" : "自主上报");
                row.createCell(3).setCellValue(maintainDTO.getItems());
                row.createCell(4).setCellValue(maintainDTO.getRemark());
                rowIdx++;
            }

            // 保存excel
            FileOutputStream fos = new FileOutputStream(tempFile);
            workbook.write(fos);
            fos.flush();
            fos.close();

            // 上传oss
            String name = filePathConfig.getFilePath(PATH, headStr, "xls");
            FileInputStream inputStream = new FileInputStream(tempFile);
            upload = fileHandler.upload(name, inputStream, FileExpirationRules.oneDay);
        } catch (Exception e) {
            logger.error("导出保养记录失败", e);
        }

        return upload;
    }

    @Override
    public ObdMaintainDTO getNeedMaintainInfo(Integer machId) throws BizException {
        logger.info("获取设备需要保养的信息服务,machId:{}", machId);
        AppMach appMach = machMapper.selectByPrimaryKey(machId);
        if (appMach == null) {
            throw new BizException(ResultEnum.NOT_FOUND.getCode(), "该设备不存在");
        }

        ObdMaintainDTO data = new ObdMaintainDTO();
        data.setMachId(appMach.getId());
        data.setMachTypeName(appMach.getMachTypeName());
        data.setMachCode(appMach.getMachCode());

        // 查找是否有系统提醒的未保养
        Integer maintainId = maintainMapper.selectNeedMaintainByMachId(machId);
        data.setMaintainId(maintainId);

        Date latestMaintainTime = maintainMapper.selectLatestMaintainTime(machId, null);
        if (latestMaintainTime != null) {
            Date endTime = new Date();
            data.setIntervalDays(DateUtil.getDaysBettweenDate(latestMaintainTime, endTime));
            int workTimes = this.getWorkTimes(machId, latestMaintainTime, endTime);
            data.setIntervalHours(MathUtil.round(1.0 * workTimes / 3600, 2));
        }
        return data;
    }

    /**
     * 获取所有的检测规则
     *
     * @return
     */
    @Deprecated
    private Map<Integer, List<MaintainRuleDTO>> getCheckRules() {
        Map<Integer, List<MaintainRuleDTO>> map = new HashMap<>();
        List<MaintainRuleDTO> ruleList = maintainRuleMapper.selectAllEnableRule();
        for (MaintainRuleDTO maintainRuleDTO : ruleList) {
            Integer deptId = maintainRuleDTO.getDeptId();
            if (map.containsKey(deptId)) {
                map.get(deptId).add(maintainRuleDTO);
            } else {
                List<MaintainRuleDTO> value = new ArrayList();
                value.add(maintainRuleDTO);
                map.put(deptId, value);
            }
        }
        return map;
    }


    /**
     * 检测是否要保养
     *
     * @param list
     * @param machDTO
     * @param ruleList
     */
    @Deprecated
    private void check(List<AppMaintain> list, AppMachDTO machDTO, List<MaintainRuleDTO> ruleList) {
        Date maintainTime = maintainMapper.selectLatestMaintainTime(machDTO.getMachId(), null);

        Double totalWorkHours = obdDataMgr.getObdTotalWorkHour(machDTO.getMachId());
        for (MaintainRuleDTO maintainRuleDTO : ruleList) {
            Integer ruleType = maintainRuleDTO.getRuleType();
            Integer value = maintainRuleDTO.getValue();
            MaintainRuleType maintainRuleType = MaintainRuleType.parseValue(ruleType);
            if (maintainRuleType == null) {
                continue;
            }

            AppMaintain appMaintain = new AppMaintain();
            Date now = new Date();
            BeanUtils.copyProperties(machDTO, appMaintain);
            appMaintain.setContent(String.format(maintainRuleType.getDetail(), value));
            appMaintain.setTriggerTime(now);

            // 检测是否需要首次保养
            if (maintainTime == null && MaintainRuleType.FIRST_INTERVAL_HOURS.getValue().equals(ruleType) &&
                    totalWorkHours >= value) {
                // 插入
                list.add(appMaintain);
                return;
            }

            // 按工作小时间隔检测
            if (maintainTime != null && MaintainRuleType.INTERVAL_HOURS.getValue().equals(ruleType)) {
                // 距离上次保养工作时长
                int workTimes = this.getWorkTimes(machDTO.getMachId(), maintainTime, now);
                if (workTimes >= value * 3600) {
                    list.add(appMaintain);
                    return;
                }
            }

            // 按间隔天数检测
            if (maintainTime != null && MaintainRuleType.INTERVAL_DAYS.getValue().equals(ruleType)) {
                int days = DateUtil.getDaysBettweenDate(maintainTime, new Date());
                if (days >= value) {
                    // 插入
                    list.add(appMaintain);
                    return;
                }
            }
        }

    }

    /**
     * 获取距离上次保养的工作时长
     *
     * @param machId
     * @param startTime
     * @param endTime
     * @return
     */
    private int getWorkTimes(Integer machId, Date startTime, Date endTime) {
        //原始数据
        List<AppDeviceObdLogDTO> logList = appDeviceObdLogDao.selectObdLogByMachId(machId, startTime, endTime);
        //硬件工时分段
        List<AppMachDaySegDTO> segList = MachSegmentUtil.separateMachDaySegment(logList);
        //计算工时工时
        int workTimes = 0;
        for (AppMachDaySegDTO segDTO : segList) {
            if (MachUtil.isRunState(segDTO.getWorkState())) {
                workTimes += segDTO.getTimes();
            }
        }
        return workTimes;
    }
}
